/* Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
var FLOWABLE = FLOWABLE || {};

/** Inspired by https://github.com/krasimir/EventBus/blob/master/src/EventBus.js */
FLOWABLE.eventBus = {

  /** Event fired when the editor is loaded and ready */
  EVENT_TYPE_EDITOR_READY: 'event-type-editor-ready',

  EVENT_TYPE_EDITOR_BOOTED: 'event-type-editor-booted',

  /** Event fired when a selection is made on the canvas. */
  /** 选中的元素发生切换 */
  EVENT_TYPE_SELECTION_CHANGE: 'event-type-selection-change',

  /** Event fired when a toolbar button has been clicked. */
  /** 点击工具栏中的按钮 */
  EVENT_TYPE_TOOLBAR_BUTTON_CLICKED: 'event-type-toolbar-button-clicked',

  /** Event fired when a stencil item is dropped on the canvas. */
  /**  */
  EVENT_TYPE_ITEM_DROPPED: 'event-type-item-dropped',

  /** Event fired when a property value is changed. */
  EVENT_TYPE_PROPERTY_VALUE_CHANGED: 'event-type-property-value-changed',

  /** Event fired on double click in canvas. */
  EVENT_TYPE_DOUBLE_CLICK: 'event-type-double-click',

  /** Event fired on a mouse out */
  EVENT_TYPE_MOUSE_OUT: 'event-type-mouse-out',

  /** Event fired on a mouse over */
  EVENT_TYPE_MOUSE_OVER: 'event-type-mouse-over',

  /** Event fired when a model is saved. */
  EVENT_TYPE_MODEL_SAVED: 'event-type-model-saved',

  /** Event fired when the quick menu buttons should be hidden. */
  EVENT_TYPE_HIDE_SHAPE_BUTTONS: 'event-type-hide-shape-buttons',

  /** Event fired when the validation popup should be shown. */
  EVENT_TYPE_SHOW_VALIDATION_POPUP: 'event-type-show-validation-popup',

  /** Event fired when a different process must be loaded. */
  EVENT_TYPE_NAVIGATE_TO_PROCESS: 'event-type-navigate-to-process',

  EVENT_TYPE_UNDO_REDO_RESET: 'event-type-undo-redo-reset',

  /** A mapping for storing the listeners*/
  listeners: {},

  /** The Oryx editor, which is stored locally to send events to */
  editor: null,

  /**
   * Add an event listener to the event bus, listening to the event with the provided type.
   * Type and callback are mandatory parameters.
   *
   * Provide scope parameter if it is important that the callback is executed
   * within a specific scope.
   */
  addListener: function (type, callback, scope) {

    // Add to the listeners map
    if (typeof this.listeners[type] !== "undefined") {
      this.listeners[type].push({
        scope: scope,
        callback: callback
      });
    } else {
      this.listeners[type] = [
        {
          scope: scope,
          callback: callback
        }
      ];
    }
  },

  /**
   * Removes the provided event listener.
   */
  removeListener: function (type, callback, scope) {
    if (typeof this.listeners[type] != "undefined") {
      var numOfCallbacks = this.listeners[type].length;
      var newArray = [];
      for (var i = 0; i < numOfCallbacks; i++) {
        var listener = this.listeners[type][i];
        if (listener.scope === scope && listener.callback === callback) {
          // Do nothing, this is the listener and doesn't need to survive
        } else {
          newArray.push(listener);
        }
      }
      this.listeners[type] = newArray;
    }
  },

  hasListener: function (type, callback, scope) {
    if (typeof this.listeners[type] != "undefined") {
      var numOfCallbacks = this.listeners[type].length;
      if (callback === undefined && scope === undefined) {
        return numOfCallbacks > 0;
      }
      for (var i = 0; i < numOfCallbacks; i++) {
        var listener = this.listeners[type][i];
        if (listener.scope == scope && listener.callback == callback) {
          return true;
        }
      }
    }
    return false;
  },

  /**
   * Dispatch an event to all event listeners registered to that specific type.
   */
  dispatch: function (type, event) {
    if (typeof this.listeners[type] != "undefined") {
      var numOfCallbacks = this.listeners[type].length;
      for (var i = 0; i < numOfCallbacks; i++) {
        var listener = this.listeners[type][i];
        if (listener && listener.callback) {
          listener.callback.apply(listener.scope, [event]);
        }
      }
    }
  },

  dispatchOryxEvent: function (event, uiObject) {
    FLOWABLE.eventBus.editor.handleEvents(event, uiObject);
  },

};

FLOWABLE.setCommands = function () {
  // 创建新节点
  FLOWABLE.CreateCommand = ORYX.Core.Command.extend({
    construct: function (option, currentReference, position, facade) {
      this.option = option;
      this.currentReference = currentReference;
      this.position = position;
      this.facade = facade;
      this.shape;
      this.edge;
      this.targetRefPos;
      this.sourceRefPos;
      /*
       * clone options parameters
       */
      this.connectedShape = option.connectedShape;
      this.connectingType = option.connectingType;
      this.namespace = option.namespace;
      this.type = option.type;
      this.containedStencil = option.containedStencil;
      this.parent = option.parent;
      this.currentReference = currentReference;
      this.shapeOptions = option.shapeOptions;
    },
    execute: function () {
      if (this.shape) {
        if (this.shape instanceof ORYX.Core.Node) {
          this.parent.add(this.shape);
          if (this.edge) {
            this.facade.getCanvas().add(this.edge);
            this.edge.dockers.first().setDockedShape(this.connectedShape);
            this.edge.dockers.first().setReferencePoint(this.sourceRefPos);
            this.edge.dockers.last().setDockedShape(this.shape);
            this.edge.dockers.last().setReferencePoint(this.targetRefPos);
          }

          this.facade.setSelection([this.shape]);

        } else if (this.shape instanceof ORYX.Core.Edge) {
          this.facade.getCanvas().add(this.shape);
          this.shape.dockers.first().setDockedShape(this.connectedShape);
          this.shape.dockers.first().setReferencePoint(this.sourceRefPos);
        }
      } else {
        this.shape = this.facade.createShape(this.option);
        this.edge = (!(this.shape instanceof ORYX.Core.Edge)) ? this.shape.getIncomingShapes().first() : undefined;
      }

      if (this.currentReference && this.position) {

        if (this.shape instanceof ORYX.Core.Edge) {

          if (!(this.currentReference instanceof ORYX.Core.Canvas)) {
            this.shape.dockers.last().setDockedShape(this.currentReference);

            if (this.currentReference.getStencil().idWithoutNs() === 'TextAnnotation') {
              var midpoint = {};
              midpoint.x = 0;
              midpoint.y = this.currentReference.bounds.height() / 2;
              this.shape.dockers.last().setReferencePoint(midpoint);
            }
            else {
              this.shape.dockers.last().setReferencePoint(this.currentReference.bounds.midPoint());
            }
          }
          else {
            this.shape.dockers.last().bounds.centerMoveTo(this.position);
          }
          this.sourceRefPos = this.shape.dockers.first().referencePoint;
          this.targetRefPos = this.shape.dockers.last().referencePoint;

        } else if (this.edge) {
          this.sourceRefPos = this.edge.dockers.first().referencePoint;
          this.targetRefPos = this.edge.dockers.last().referencePoint;
        }
      } else {
        var containedStencil = this.containedStencil;
        var connectedShape = this.connectedShape;
        var bc = connectedShape.bounds;
        var bs = this.shape.bounds;

        var pos = bc.center();
        if (containedStencil.defaultAlign() === "north") {
          pos.y -= (bc.height() / 2) + ORYX.CONFIG.SHAPEMENU_CREATE_OFFSET + (bs.height() / 2);
        } else if (containedStencil.defaultAlign() === "northeast") {
          pos.x += (bc.width() / 2) + ORYX.CONFIG.SHAPEMENU_CREATE_OFFSET_CORNER + (bs.width() / 2);
          pos.y -= (bc.height() / 2) + ORYX.CONFIG.SHAPEMENU_CREATE_OFFSET_CORNER + (bs.height() / 2);
        } else if (containedStencil.defaultAlign() === "southeast") {
          pos.x += (bc.width() / 2) + ORYX.CONFIG.SHAPEMENU_CREATE_OFFSET_CORNER + (bs.width() / 2);
          pos.y += (bc.height() / 2) + ORYX.CONFIG.SHAPEMENU_CREATE_OFFSET_CORNER + (bs.height() / 2);
        } else if (containedStencil.defaultAlign() === "south") {
          pos.y += (bc.height() / 2) + ORYX.CONFIG.SHAPEMENU_CREATE_OFFSET + (bs.height() / 2);
        } else if (containedStencil.defaultAlign() === "southwest") {
          pos.x -= (bc.width() / 2) + ORYX.CONFIG.SHAPEMENU_CREATE_OFFSET_CORNER + (bs.width() / 2);
          pos.y += (bc.height() / 2) + ORYX.CONFIG.SHAPEMENU_CREATE_OFFSET_CORNER + (bs.height() / 2);
        } else if (containedStencil.defaultAlign() === "west") {
          pos.x -= (bc.width() / 2) + ORYX.CONFIG.SHAPEMENU_CREATE_OFFSET + (bs.width() / 2);
        } else if (containedStencil.defaultAlign() === "northwest") {
          pos.x -= (bc.width() / 2) + ORYX.CONFIG.SHAPEMENU_CREATE_OFFSET_CORNER + (bs.width() / 2);
          pos.y -= (bc.height() / 2) + ORYX.CONFIG.SHAPEMENU_CREATE_OFFSET_CORNER + (bs.height() / 2);
        } else {
          pos.x += (bc.width() / 2) + ORYX.CONFIG.SHAPEMENU_CREATE_OFFSET + (bs.width() / 2);
        }

        // Move shape to the new position
        this.shape.bounds.centerMoveTo(pos);

        // Move all dockers of a node to the position
        if (this.shape instanceof ORYX.Core.Node) {
          (this.shape.dockers || []).each(function (docker) {
            docker.bounds.centerMoveTo(pos);
          });
        }

        //this.shape.update();
        this.position = pos;

        if (this.edge) {
          this.sourceRefPos = this.edge.dockers.first().referencePoint;
          this.targetRefPos = this.edge.dockers.last().referencePoint;
        }
      }

      this.facade.getCanvas().update();
      this.facade.updateSelection();

    },
    rollback: function () {
      this.facade.deleteShape(this.shape);
      if (this.edge) {
        this.facade.deleteShape(this.edge);
      }
      //this.currentParent.update();
      this.facade.setSelection(this.facade.getSelection().without(this.shape, this.edge));
    }
  });

  // 修改节点属性
  FLOWABLE.MorphToCommand = ORYX.Core.Command.extend({
    construct: function (shape, stencil, facade) {
      this.shape = shape;
      this.stencil = stencil;
      this.facade = facade;
    },
    execute: function () {
      var shape = this.shape;
      var stencil = this.stencil;
      var resourceId = shape.resourceId;
      // Serialize all attributes
      var serialized = shape.serialize();
      stencil.properties().each((function (prop) {
        if (prop.readonly()) {
          serialized = serialized.reject(function (serProp) {
            return serProp.name == prop.id();
          });
        }
      }).bind(this));
      var newShape
      // Get shape if already created, otherwise create a new shape
      if (this.newShape) {
        newShape = this.newShape;
        this.facade.getCanvas().add(newShape);
      } else {
        newShape = this.facade.createShape({
          type: stencil.id(),
          namespace: stencil.namespace(),
          resourceId: resourceId
        });
      }
      // calculate new bounds using old shape's upperLeft and new shape's width/height
      var boundsObj = serialized.find(function (serProp) {
        return (serProp.prefix === "oryx" && serProp.name === "bounds");
      });
      var changedBounds = null;
      if (!this.facade.getRules().preserveBounds(shape.getStencil())) {
        var bounds = boundsObj.value.split(",");
        if (parseInt(bounds[0], 10) > parseInt(bounds[2], 10)) { // if lowerRight comes first, swap array items
          var tmp = bounds[0];
          bounds[0] = bounds[2];
          bounds[2] = tmp;
          tmp = bounds[1];
          bounds[1] = bounds[3];
          bounds[3] = tmp;
        }
        bounds[2] = parseInt(bounds[0], 10) + newShape.bounds.width();
        bounds[3] = parseInt(bounds[1], 10) + newShape.bounds.height();
        boundsObj.value = bounds.join(",");
      } else {
        var height = shape.bounds.height();
        var width = shape.bounds.width();
        // consider the minimum and maximum size of
        // the new shape
        if (newShape.minimumSize) {
          if (shape.bounds.height() < newShape.minimumSize.height) {
            height = newShape.minimumSize.height;
          }


          if (shape.bounds.width() < newShape.minimumSize.width) {
            width = newShape.minimumSize.width;
          }
        }
        if (newShape.maximumSize) {
          if (shape.bounds.height() > newShape.maximumSize.height) {
            height = newShape.maximumSize.height;
          }

          if (shape.bounds.width() > newShape.maximumSize.width) {
            width = newShape.maximumSize.width;
          }
        }
        changedBounds = {
          a: {
            x: shape.bounds.a.x,
            y: shape.bounds.a.y
          },
          b: {
            x: shape.bounds.a.x + width,
            y: shape.bounds.a.y + height
          }
        };
      }
      var oPos = shape.bounds.center();
      if (changedBounds !== null) {
        newShape.bounds.set(changedBounds);
      }
      // Set all related dockers
      this.setRelatedDockers(shape, newShape);
      // store DOM position of old shape
      var parentNode = shape.node.parentNode;
      var nextSibling = shape.node.nextSibling;
      // Delete the old shape
      this.facade.deleteShape(shape);
      // Deserialize the new shape - Set all attributes
      newShape.deserialize(serialized);
      /*
       * Change color to default if unchanged
       * 23.04.2010
       */
      if (shape.getStencil().property("oryx-bgcolor")
        && shape.properties["oryx-bgcolor"]
        && shape.getStencil().property("oryx-bgcolor").value().toUpperCase() == shape.properties["oryx-bgcolor"].toUpperCase()) {
        if (newShape.getStencil().property("oryx-bgcolor")) {
          newShape.setProperty("oryx-bgcolor", newShape.getStencil().property("oryx-bgcolor").value());
        }
      }
      if (changedBounds !== null) {
        newShape.bounds.set(changedBounds);
      }
      if (newShape.getStencil().type() === "edge" || (newShape.dockers.length == 0 || !newShape.dockers[0].getDockedShape())) {
        newShape.bounds.centerMoveTo(oPos);
      }
      if (newShape.getStencil().type() === "node" && (newShape.dockers.length == 0 || !newShape.dockers[0].getDockedShape())) {
        this.setRelatedDockers(newShape, newShape);
      }
      // place at the DOM position of the old shape
      if (nextSibling) parentNode.insertBefore(newShape.node, nextSibling);
      else parentNode.appendChild(newShape.node);
      // Set selection
      this.facade.setSelection([newShape]);
      this.facade.getCanvas().update();
      this.facade.updateSelection();
      this.newShape = newShape;

    },
    rollback: function () {
      if (!this.shape || !this.newShape || !this.newShape.parent) { return; }
      // Append shape to the parent
      this.newShape.parent.add(this.shape);
      // Set dockers
      this.setRelatedDockers(this.newShape, this.shape);
      // Delete new shape
      this.facade.deleteShape(this.newShape);
      // Set selection
      this.facade.setSelection([this.shape]);
      // Update
      this.facade.getCanvas().update();
      this.facade.updateSelection();
    },
    /**
     * Set all incoming and outgoing edges from the shape to the new shape
     * @param {Shape} shape
     * @param {Shape} newShape
     */
    setRelatedDockers: function (shape, newShape) {

      if (shape.getStencil().type() === "node") {

        (shape.incoming || []).concat(shape.outgoing || [])
          .each(function (i) {
            i.dockers.each(function (docker) {
              if (docker.getDockedShape() == shape) {
                var rPoint = Object.clone(docker.referencePoint);
                // Move reference point per percent

                var rPointNew = {
                  x: rPoint.x * newShape.bounds.width() / shape.bounds.width(),
                  y: rPoint.y * newShape.bounds.height() / shape.bounds.height()
                };

                docker.setDockedShape(newShape);
                // Set reference point and center to new position
                docker.setReferencePoint(rPointNew);
                if (i instanceof ORYX.Core.Edge) {
                  docker.bounds.centerMoveTo(rPointNew);
                } else {
                  var absXY = shape.absoluteXY();
                  docker.bounds.centerMoveTo({ x: rPointNew.x + absXY.x, y: rPointNew.y + absXY.y });
                  //docker.bounds.moveBy({x:rPointNew.x-rPoint.x, y:rPointNew.y-rPoint.y});
                }
              }
            });
          });

        // for attached events
        if (shape.dockers.length > 0 && shape.dockers.first().getDockedShape()) {
          newShape.dockers.first().setDockedShape(shape.dockers.first().getDockedShape());
          newShape.dockers.first().setReferencePoint(Object.clone(shape.dockers.first().referencePoint));
        }

      } else { // is edge
        newShape.dockers.first().setDockedShape(shape.dockers.first().getDockedShape());
        newShape.dockers.first().setReferencePoint(shape.dockers.first().referencePoint);
        newShape.dockers.last().setDockedShape(shape.dockers.last().getDockedShape());
        newShape.dockers.last().setReferencePoint(shape.dockers.last().referencePoint);
      }
    }
  });

  // 修改属性
  FLOWABLE.ChangePropertyCommand = ORYX.Core.Command.extend({
    construct: function (key, oldValue, newValue, shape, facade) {
      this.key = key;
      this.oldValue = oldValue;
      this.newValue = newValue;
      this.shape = shape;
      this.facade = facade;
    },
    execute: function () {
      this.shape.setProperty(this.key, this.newValue);
      this.facade.getCanvas().update();
      this.facade.updateSelection();
    },
    rollback: function () {
      this.shape.setProperty(this.key, this.oldValue);
      this.facade.getCanvas().update();
      this.facade.updateSelection();
    }
  })

  // 拖拽画板创建
  FLOWABLE.CreatePalette = ORYX.Core.Command.extend({
    construct: function (option, dockedShape, canAttach, position, facade) {
      this.option = option;
      this.docker = null;
      this.dockedShape = dockedShape;
      this.dockedShapeParent = dockedShape.parent || facade.getCanvas();
      this.position = position;
      this.facade = facade;
      this.selection = this.facade.getSelection();
      this.shape = null;
      this.parent = null;
      this.canAttach = canAttach;
    },
    execute: function () {
      if (!this.shape) {
        this.shape = this.facade.createShape(this.option);
        this.parent = this.shape.parent;
      } else if (this.parent) {
        this.parent.add(this.shape);
      }
      if (this.canAttach && this.shape.dockers && this.shape.dockers.length) {
        this.docker = this.shape.dockers[0];
        this.dockedShapeParent.add(this.docker.parent);
        // Set the Docker to the new Shape
        this.docker.setDockedShape(undefined);
        this.docker.bounds.centerMoveTo(this.position);
        if (this.dockedShape !== this.facade.getCanvas()) {
          this.docker.setDockedShape(this.dockedShape);
        }
        this.facade.setSelection([this.docker.parent]);
      }
      this.facade.getCanvas().update();
      this.facade.updateSelection();
    },
    rollback: function () {
      if (this.shape) {
        this.facade.setSelection(this.selection.without(this.shape));
        this.facade.deleteShape(this.shape);
      }
      if (this.canAttach && this.docker) {
        this.docker.setDockedShape(undefined);
      }
      this.facade.getCanvas().update();
      this.facade.updateSelection();
    }
  });
}

export default FLOWABLE